/*
 * State.java
 *
 * Created on 10 de Julho de 2011, 11:32
 *
 */

package intopo.demonstrator.partnerclassification;

import util.math.Rational;

import util.adt.WeightedCollection;
import util.adt.O1StableLIFGCollectionWeight;

/**
 * A state of the partner classification model.
 *
 * @author <a href="mailto:mariano.pedro@gmail.com">Pedro Mariano</a>
 *
 * @version 1.0 10 de Julho de 2011
 */
public class State
	extends intopo.partnerselection.AbstractProbabilityVector<State.ProbabilityCombination>
{
	/**
	 * The parameters of the partner classification model.
	 */
	final Parameters parameters;
	/**
	 * The partners and their weights.
	 */
	final WeightedCollection<Partner> partners;
	/**
	 * The index of the combination last selected in method {@code transit()}.
	 */
	private int selectedCombination;
	/**
	 * Creates a probability vector that is initialised in constructor {@code AbstractProbabilityVector(S[],int,MersenneTwister)}.
	 *
	 * <p> This method is used to instanciate the vector that is passed to the constructor of class {@code AbstractProbabilityVector}.  The constructors of this class have a parameter of type {@code S[]}. It is a generic type array.
	 */
	static private ProbabilityCombination[] createVector (int size)
	{
		ProbabilityCombination[] result = new ProbabilityCombination [size];
		while (size > 0) {
			size--;
			result [size] = new ProbabilityCombination ();
		}
		return result;
	}
	/**
	 * Creates a probability vector that is used by constructor {@code AbstractProbabilityVector(S[])}.
	 *
	 * <p> This method is used to instanciate the vector that is passed to the constructor of class {@code AbstractProbabilityVector}.  The constructors of this class have a parameter of type {@code S[]}. It is a generic type array.
	 */
	static private ProbabilityCombination[] copyVector (State other)
	{
		ProbabilityCombination[] result;
		int size;
		size = other.size ();
		result = new ProbabilityCombination [size];
		while (size > 0) {
			size--;
			result [size] = new ProbabilityCombination (other.getSlot (size));
		}
		return result;
	}
	/**
	 * Creates a new instance of State
	 */
	public State (Parameters parameters)
	{
		super (createVector (parameters.getPoolSize ()), parameters.getMaxProbability (), Cache.supply);
		this.parameters = parameters;
		this.partners = new O1StableLIFGCollectionWeight<Partner> (parameters.getNumberPartners ());
		this.selectedCombination = -1;
		// initialise the set of candidate partners
		for (Partner p : parameters.getPartners ()) {
			this.partners.add (p, (short) (parameters.getMaxWeight () / 2));
		}
		// initialise the combination vector
		for (int i = parameters.getPoolSize () - 1; i >= 0; i--) {
			this.getSlot (i).combination = new PartnerCombination (parameters.getCombinationSize (), this.partners);
		}
	}
	/**
	 * Copy constructor used when calculating the next state of the partner classification model
	 */
	private State (State state)
	{
		super (copyVector (state));
		this.parameters = state.parameters;
		this.partners = new O1StableLIFGCollectionWeight<Partner> (parameters.getNumberPartners ());
		this.selectedCombination = -1;
		// initialise the set of candidate partners
		for (Partner p : state.partners) {
			this.partners.add (p, state.partners.getWeight (p));
		}
	}
//	/**
//	 * Return the index of partner combination randomly selected using the
//	 * probability vector.
//	 */
//	int randomCombination ()
//	{
//		int rnd = Math.abs (Cache.supply.nextInt ()) % (parameters.getMaxProbability () + 1);
//		int lower = 0, upper = this.probabilityCombinationVectors.length - 1;
//		while (upper >= lower) {
//			int middle = lower + (upper - lower) / 2;
//			if (this.probabilityCombinationVectors [middle].probability < rnd) {
//				lower = middle + 1;
//			}
//			else {
//				upper = middle - 1;
//			}
//		}
//		return Math.max (0, Math.min (lower, this.probabilityCombinationVectors.length - 1));
//	}
	/**
	 * Perform a transition of the <i>partner classification</i> model.
	 * <p> Select a combination of partners using the probability vector.  Update the weight of selected partners.  If the combination behaves acceptably, nothing happens.
	 */
	public State getTransition (Statistics statistics)
	{
		State result = new State (this);
		result.updateState (statistics);
		return result;
//		this.selectedCombination = this.randomCombination (Cache.supply);
//		int payoff;
//		if (!this.getSlot (this.selectedCombination).combination.eventBehaveAcceptably ()) {
//			int remainderIdx = Math.abs (Cache.supply.nextInt ()) % (this.parameters.getPoolSize () - 1);
//			if (remainderIdx >= this.selectedCombination) {
//				remainderIdx++;
//			}
//			int decrease = this.getRawProbability (this.selectedCombination)
//				* this.parameters.getDeltaNumerator ()
//				/ this.parameters.getDeltaDenominator ();
//			result.distributeProbability (this.selectedCombination, decrease, remainderIdx);
//			result.getSlot (this.selectedCombination).combination
//				= new PartnerCombination (parameters.getCombinationSize (), this.partners);
//			payoff = 0;
//		}
//		else {
//			payoff = 1;
//		}
//		// update the partners' weight (in the returned state, using the info in this state)
//		int probability = this.getRawProbability (this.selectedCombination);
//		for (Partner p : this.getSlot (this.selectedCombination).combination.combination) {
////			long ow = partners.getWeight (p) - 1;
////			long nw = (long) (payoff * (parameters.getMaxWeight () - 1));
////			long w1 = ow * (parameters.getMaxProbability () - probability) / parameters.getMaxProbability ();
////			long w2 = nw * probability / parameters.getMaxProbability ();
//			long ow = partners.getWeight (p);
//			long nw = (long) (payoff * parameters.getMaxWeight ());
//			long w1 = (ow * (parameters.getMaxProbability () - probability)) / parameters.getMaxProbability ();
//			long w2 = (nw * probability) / parameters.getMaxProbability ();
//			short weight = (short) (w1 + w2);
//			result.partners.setWeight (p, weight);
//		}
//		// update the statistics
//		for (Partner p : this.getSlot (selectedCombination).combination.combination) {
//			statistics.getPartnerData (p).gamesPlayed++;
//		}
//		statistics.gamesPlayed++;
//		return result;
	}
	public void applyTransition (Statistics statistics)
	{
		this.updateState (statistics);
	}
	/**
	 * Update this state by applying a transition.
	 */
	private void updateState (Statistics statistics)
	{
		this.selectedCombination = this.randomCombination (Cache.supply);
		int payoff;
		if (!this.getSlot (this.selectedCombination).combination.eventBehaveAcceptably ()) {
			int remainderIdx = Math.abs (Cache.supply.nextInt ()) % (this.parameters.getPoolSize () - 1);
			if (remainderIdx >= this.selectedCombination) {
				remainderIdx++;
			}
			int decrease = this.getRawProbability (this.selectedCombination)
				* this.parameters.getDeltaNumerator ()
				/ this.parameters.getDeltaDenominator ();
			this.distributeProbability (this.selectedCombination, decrease, remainderIdx);
			this.getSlot (this.selectedCombination).combination
				= new PartnerCombination (parameters.getCombinationSize (), this.partners);
			payoff = 0;
		}
		else {
			payoff = 1;
		}
		// update the partners' weight (in the returned state, using the info in this state)
		int probability = this.getRawProbability (this.selectedCombination);
		for (Partner p : this.getSlot (this.selectedCombination).combination.combination) {
//			long ow = partners.getWeight (p) - 1;
//			long nw = (long) (payoff * (parameters.getMaxWeight () - 1));
//			long w1 = ow * (parameters.getMaxProbability () - probability) / parameters.getMaxProbability ();
//			long w2 = nw * probability / parameters.getMaxProbability ();
			long ow = partners.getWeight (p);
			long nw = (long) (payoff * parameters.getMaxWeight ());
			long w1 = (ow * (parameters.getMaxProbability () - probability)) / parameters.getMaxProbability ();
			long w2 = (nw * probability) / parameters.getMaxProbability ();
			short weight = (short) (w1 + w2);
			this.partners.setWeight (p, weight);
		}
		// update the statistics
		for (Partner p : this.getSlot (selectedCombination).combination.combination) {
			statistics.getPartnerData (p).gamesPlayed++;
		}
		statistics.gamesPlayed++;
	}
	/**
	 * Get the index of the combination last selected in method {@code transit()}.
	 */
	final public int getSelectedCombination ()
	{
		return this.selectedCombination;
	}
	/**
	 * Return the combination in the given index of the combination vector.
	 */
	final public PartnerCombination getCombination (int index)
	{
		return this.getSlot (index).combination;
	}
	/**
	 * Get the internal representation of the weight of selecting the given partner.  Weights are represented by integers in {@code short} precision.  Moreover, only a portion of the bits are used as determined by {@code parameters.getNumberBitsWeight()}.
	 * @see Parameters#getNumberBitsWeight()
	 * @param partner The partner whose weight is returned.
	 * @return The weight of selecting the given partner
	 */
	final public short getRawWeight (Partner partner)
	{
		return this.partners.getWeight (partner);
	}
	/**
	 * Return the weight of selecting the given partner as a floating point number (in {@code float} precision) in the interval [0,1].
	 */
	final public float getWeightAsFloat (Partner partner)
	{
		float numerator = this.partners.getWeight (partner);
		return numerator / parameters.getMaxWeight ();
	}
	/**
	 * Return the weight of selecting the given partner as a floating point number (in {@code double} precision) in the interval [0,1].
	 */
	final public double getWeightAsDouble (Partner partner)
	{
		double numerator = this.partners.getWeight (partner);
		return numerator / parameters.getMaxWeight ();
	}
	/**
	 * Return the weight of selecting the given partner as a rational number in the interval [0,1].
	 */
	final public Rational getWeightAsRational (Partner partner)
	{
		int numerator = this.partners.getWeight (partner);
		return new Rational (numerator, parameters.getMaxWeight ()).simplify ();
	}
	/**
	 * Represents a slot of the probability and combination vectors.
	 */
	static public class ProbabilityCombination
		extends intopo.partnerselection.AbstractProbabilityVector.Slot
		implements Cloneable
	{
		/**
		 * The combination of partners in this slot of the combination vector.
		 */
		private PartnerCombination combination;
		private ProbabilityCombination ()
		{
			super (0);
		}
//		/**
//		 * Construct a slot of the probability and combination vectors.
//		 */
//		private ProbabilityCombination (int probability, PartnerCombination combination)
//		{
//			super (probability);
//			this.combination = combination;
//		}
//		/**
//		 * Construct a slot of the probability and combination vectors.
//		 */
//		private ProbabilityCombination (int probability, int combinationSize, WeightedCollection<Partner> partners)
//		{
//			this (probability, new PartnerCombination (combinationSize, partners));
//		}
		/**
		 * Copy constructor used by {@code clone()} method.
		 */
		private ProbabilityCombination (ProbabilityCombination other)
		{
			super (other);
			this.combination = other.combination;
		}
		public ProbabilityCombination clone ()
		{
			return new ProbabilityCombination (this);
		}
	}
}

// Local Variables:
// mode: java
// mode: flyspell-prog
// ispell-local-dictionary: "british"
// End:
